/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//#define USE_TEXT_CACHE		//!< テキストテクスチャをキャッシングします

#define INCLUDE_FROM_OBJC_C
#import "../../component.h"
#import "../../graphicDevice.h"
#import "text_impl.h"
#import "Texture2D.h"
#import <assert.h>

namespace Graph2D
{
	// 循環リストクラス
	class CircularlyLinkedList
	{
		CircularlyLinkedList* previous;
		CircularlyLinkedList* next;
	public:
		CircularlyLinkedList();
		virtual ~CircularlyLinkedList();

		CircularlyLinkedList* getBack();
		CircularlyLinkedList* getBefore();
		void pushBack(CircularlyLinkedList* child);
		void pushBefore(CircularlyLinkedList* parent);
		void remove();
		void check() const;
	};

	CircularlyLinkedList::CircularlyLinkedList()
	{
		previous = next = this;
	}
	CircularlyLinkedList::~CircularlyLinkedList()
	{
		remove();
	}
	CircularlyLinkedList* CircularlyLinkedList::getBack()
	{
		return next;
	}
	CircularlyLinkedList* CircularlyLinkedList::getBefore()
	{
		return previous;
	}
	void CircularlyLinkedList::pushBack(CircularlyLinkedList* child)
	{
		child->previous = this;
		next->previous = child;
		child->next = next;
		next = child;
	}
	void CircularlyLinkedList::pushBefore(CircularlyLinkedList* parent)
	{
		parent->next = this;
		previous->next = parent;
		parent->previous = previous;
		previous = parent;
	}
	void CircularlyLinkedList::remove()
	{
		previous->next = next;
		next->previous = previous;
	}
	void CircularlyLinkedList::check() const
	{
		for(const CircularlyLinkedList* block = next; block != this; block = block->next)
		{
			assert(block->previous->next == block);
			assert(block->next->previous == block);
		}
	}



	namespace Implementation
	{
		namespace Text
		{
			typedef struct Environment
			{
				UIFont* font;
				UITextAlignment alignment;
				Texture2D* texture2D;
			}Environment;

			//////////////////////////////////////////////////////////////////////
			// キャッシュはフォントファミリーを考慮していないので注意が必要
			namespace Cache
			{
#if defined(USE_TEXT_CACHE)
				class Entry : public CircularlyLinkedList
				{
					std::string text;
					size_t size;
					Texture2D* texture;		//!< nilならばこのエントリーは未使用

				public:
					Entry() : size(0), texture(NULL)
					{
					}
					~Entry()
					{
						clear();
					}
					void clear()
					{
						if(texture)
						{
							[texture release];
							texture = nil;

							text.clear();
						}
					}
					Texture2D* getTexture()
					{
						return texture;
					}
					size_t getSize()
					{
						return size;
					}
					std::string& getText()
					{
						return text;
					}
					Texture2D* set(Environment* environment, const float width, const float height, const std::string& text)
					{
						clear();

						if(!text.empty())
						{
							NSString* nsString = [NSString stringWithUTF8String:text.c_str()];

							texture = [[Texture2D alloc]
								initWithString:nsString
								dimensions:CGSizeMake(width, height)
								alignment:environment->alignment
								fontName:environment->font.fontName
								fontSize:environment->font.pointSize
							];

							this->text = text;
							this->size = environment->font.pointSize;
						}
						return texture;
					}
				};

				static CircularlyLinkedList root;
#endif
				void initialize(const size_t size)
				{
#if defined(USE_TEXT_CACHE)
					finalize();

					for(size_t i = 0; i < size; i++)
					{
						root.pushBack(new Entry());
					}
#endif
				}

				void finalize()
				{
#if defined(USE_TEXT_CACHE)
					CircularlyLinkedList* next = root.getBack();
					while(next != &root)
					{
						Entry* entry = (Entry*)(next);
						next = entry->getBack();
						entry->remove();
						delete entry;
					}
#endif
				}

#if defined(USE_TEXT_CACHE)
				Texture2D* find(const std::string& text, const size_t size)
				{
					for(CircularlyLinkedList* list = root.getBack(); list != &root; list = list->getBack())
					{
						Entry* entry = (Entry*)(list);
						if(entry->getTexture() && (entry->getSize() == size) && (entry->getText() == text))
						{
							// 発見したエントリーを先頭へ移動
							entry->remove();
							root.pushBack(entry);

							return entry->getTexture();
						}
					}
					return NULL;
				}

				Texture2D* regist(Environment* environment, const float width, const float height, const std::string& text)
				{
					// 末尾からエントリーを削除
					Entry* entry = (Entry*)(root.getBefore());
					entry->remove();

					// キャッシュエントリーを更新
					entry->set(environment, width, height, text);

					// 先頭にエントリーを追加
					root.pushBack(entry);

					return entry->getTexture();
				}
#endif
			}
			//////////////////////////////////////////////////////////////////////

			void* create()
			{
				return create([UIFont systemFontSize]);
			}

			void* create(const size_t fontSize)
			{
				Environment* environment = (Environment*)malloc(sizeof(Environment));
				if(environment)
				{
					environment->font = [[UIFont systemFontOfSize:fontSize] retain];
					environment->texture2D = nil;
					environment->alignment = UITextAlignmentLeft;
				}
				return environment;
			}

			void* create(const std::string& fontName, const size_t fontSize)
			{
				Environment* environment = (Environment*)malloc(sizeof(Environment));
				if(environment)
				{
					NSString* name = [NSString stringWithUTF8String:fontName.c_str()];
					environment->font = [UIFont fontWithName:name size:fontSize];
					environment->texture2D = nil;
					environment->alignment = UITextAlignmentLeft;
				}
				return environment;
			}

			void release(void* self)
			{
				Environment* environment = (Environment*)self;
				if(environment)
				{
					if(environment->font)
					{
						[environment->font release];
						environment->font = nil;
					}
					if(environment->texture2D)
					{
						[environment->texture2D release];
						environment->texture2D = nil;
					}
					free(environment);
				}
			}

			GLuint getName(void* self)
			{
				Environment* environment = (Environment*)self;
				return environment->texture2D ? environment->texture2D.name : 0;
			}

			static size_t to_i(const size_t base, const float scale)
			{
				return static_cast<const size_t>(static_cast<float>(base) * scale + 0.5f);
			}

			size_t getFontSize(void* self)
			{
				Environment* environment = reinterpret_cast<Environment*>(self);
				return environment->font.pointSize;
			}

			void setFontSize(void* self, const size_t fontSize)
			{
				Environment* environment = (Environment*)self;
				UIFont* lastFont = environment->font;
				environment->font = [[lastFont fontWithSize:fontSize] retain];
				[lastFont release];
			}

			const char* getFontName(void* self)
			{
				Environment* environment = (Environment*)self;
				return [environment->font.fontName UTF8String];
			}

			void setFontName(void* self, const std::string& fontName)
			{
				Environment* environment = (Environment*)self;
				if(environment->font)
				{
					[environment->font release];
				}

				CGFloat fontSize = environment->font.pointSize;
				NSString* name = [NSString stringWithUTF8String:fontName.c_str()];
				environment->font = [UIFont fontWithName:name size:fontSize];
			}

			Vector2 getSize(void* self, const std::string& text, const Vector2& clientSize)
			{
				if(text.empty())
				{
					return Vector2(1, 1);
				}
				else
				{
					NSString* nsString = [NSString stringWithUTF8String:text.c_str()];
					Environment* environment = (Environment*)self;
					
					const CGSize maximumSize = CGSizeMake(clientSize.x, clientSize.y);
					//const CGSize dimensions = [nsString sizeWithFont:environment->font constrainedToSize:maximumSize lineBreakMode:UILineBreakModeWordWrap];
					//const CGSize dimensions = [nsString sizeWithFont:environment->font constrainedToSize:maximumSize lineBreakMode:UILineBreakModeCharacterWrap];
					const CGSize dimensions = [nsString sizeWithFont:environment->font constrainedToSize:maximumSize lineBreakMode:UILineBreakModeClip];
					//const CGSize dimensions = maximumSize;
					
					//NSLog(@"%@ %@(%f) %f, %f\n", nsString, environment->font.fontName, environment->font.pointSize, dimensions.width, dimensions.height);

					return Vector2(dimensions.width, dimensions.height);
				}
			}
			
			void setAlignment(void* self, const unsigned char alignment)
			{
				Environment* environment = (Environment*)self;
				switch(alignment)
				{
				case TEXT_ALIGNMENT_LEFT:
					environment->alignment = UITextAlignmentLeft;
					break;
				case TEXT_ALIGNMENT_CENTER:
					environment->alignment = UITextAlignmentCenter;
					break;
				case TEXT_ALIGNMENT_RIGHT:
					environment->alignment = UITextAlignmentRight;
					break;
				}
			}

			bool set(void* self, const float width, const float height, const std::string& text)
			{
				Environment* environment = (Environment*)self;

#if defined(USE_TEXT_CACHE)
				if(environment->texture2D)
				{
					[environment->texture2D release];
					environment->texture2D = nil;
				}

				environment->texture2D = Cache::find(text, environment->font.pointSize);
				if(environment->texture2D == nil)
				{
					environment->texture2D = Cache::regist(environment, width, height, text);
				}
				if(environment->texture2D)
				{
					[environment->texture2D retain];
				}
#else
				if(environment->texture2D)
				{
					[environment->texture2D release];
					environment->texture2D = nil;
				}
				if(text.empty())
					return false;

				NSString* nsString = [NSString stringWithUTF8String:text.c_str()];
				environment->texture2D = [[Texture2D alloc]
					initWithString:nsString
					dimensions:CGSizeMake(width, height)
					alignment:environment->alignment
					fontName:environment->font.fontName
					fontSize:environment->font.pointSize
				];

				return true;
#endif
			}

			void draw(void* self, const Vector2& position, const Vector2& size)
			{
				Environment* environment = (Environment*)self;
				if(environment->texture2D)
				{
					CGRect rect = CGRectMake(
						position.x,
						position.y,
						size.x,
						size.y
					);
					[environment->texture2D drawInRect:rect];
				}
			}
		}
	}
}
